{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "先导入必要的包"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import pandas as pd\n",
    "import mglearn\n",
    "\n",
    "%matplotlib inline "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "线性模型是在实践中广泛使用的一类模型，几十年来被广泛研究，它可以追溯到一百多年前。线性模型利用输入特征的线性函数（linear function）进行预测，稍后会对此进行解释。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1. 用于回归的线性模型\n",
    "对于回归问题，线性模型预测的一般公式如下：\n",
    "\n",
    "$$ŷ = w[0] * x[0] + w[1] * x[1] + … + w[p] * x[p] + b$$\n",
    "\n",
    "这里 $x[0]$ 到 $x[p]$ 表示单个数据点的特征（本例中特征个数为 $p+1$），$w$ 和 $b$ 是学习模型的\n",
    "参数，$ŷ$ 是模型的预测结果。对于单一特征的数据集，公式如下：\n",
    "\n",
    "$$ŷ = w[0] * x[0] + b$$\n",
    "\n",
    "你可能还记得，这就是高中数学里的直线方程。这里 $w[0]$ 是斜率，$b$ 是 $y$ 轴偏移。对于有\n",
    "更多特征的数据集，$w$ 包含沿每个特征坐标轴的斜率。或者，你也可以将预测的响应值看\n",
    "作输入特征的加权求和，权重由 $w$ 的元素给出（可以取负值）。\n",
    "下列代码可以在一维 wave 数据集上学习参数 $w[0]$ 和 $b$："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "mglearn.plots.plot_linear_regression_wave()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center><img src=\"imgs/2-11.jpg\" width=50%></center>\n",
    "<center><b>图 2-11：线性模型对 wave 数据集的预测结果</b></center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们在图中添加了坐标网格，便于理解直线的含义。从 w[0] 可以看出，斜率应该在 0.4 左右，在图像中也可以直观地确认这一点。截距是指预测直线与 y 轴的交点：比 0 略小，也可以在图像中确认。\n",
    "\n",
    "用于回归的线性模型可以表示为这样的回归模型：对单一特征的预测结果是一条直线，两个特征时是一个平面，或者在更高维度（即更多特征）时是一个超平面。\n",
    "\n",
    "如果将直线的预测结果与上一章图 2-10 中 KNeighborsRegressor 的预测结果进行比较，你会发现直线的预测能力非常受限。似乎数据的所有细节都丢失了。从某种意义上来说，这种说法是正确的。假设目标 y 是特征的线性组合，这是一个非常强的（也有点不现实的）假设。但观察一维数据得出的观点有些片面。对于有多个特征的数据集而言，线性模型可以非常强大。特别地，如果特征数量大于训练数据点的数量，任何目标 y 都可以（在训练集上）用线性函数完美拟合。\n",
    "\n",
    "有许多不同的线性回归模型。这些模型之间的区别在于如何从训练数据中学习参数 w 和 b，以及如何控制模型复杂度。下面介绍最常见的线性回归模型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2. 线性回归（又名普通最小二乘法）\n",
    "线性回归，或者普通最小二乘法（ordinary least squares，OLS），是回归问题最简单也最经典的线性方法。线性回归寻找参数 w 和 b，使得对训练集的预测值与真实的回归目标值 y 之间的均方误差最小。均方误差（mean squared error）是预测值与真实值之差的平方和除以样本数。线性回归没有参数，这是一个优点，但也因此无法控制模型的复杂度。\n",
    "\n",
    "下列代码可以生成图 2-11 中的模型："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from sklearn.linear_model import LinearRegression\n",
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "X, y = mglearn.datasets.make_wave(n_samples=60)\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)\n",
    "\n",
    "lr = LinearRegression().fit(X_train, y_train)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "“斜率”参数（w，也叫作权重或系数）被保存在 coef\\_ 属性中，而偏移或截距（b）被保存在 intercept\\_ 属性中："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "print(\"lr.coef_: {}\".format(lr.coef_))\n",
    "print(\"lr.intercept_: {}\".format(lr.intercept_))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "你可能注意到了 coef\\_ 和 intercept\\_ 结尾处奇怪的下划线。scikit-learn 总是将从训练数据中得出的值保存在以下划线结尾的属性中。这是为了将其与用户设置的参数区分开。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "intercept\\_ 属性是一个浮点数，而 coef\\_ 属性是一个 NumPy 数组，每个元素对应一个输入特征。由于 wave 数据集中只有一个输入特征，所以 lr.coef\\_ 中只有一个元素。我们来看一下训练集和测试集的性能："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "print(\"Training set score: {:.2f}\".format(lr.score(X_train, y_train)))\n",
    "print(\"Test set score: {:.2f}\".format(lr.score(X_test, y_test)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$R^2$ 约为 0.66，这个结果不是很好，但我们可以看到，训练集和测试集上的分数非常接近。这说明可能存在欠拟合，而不是过拟合。对于这个一维数据集来说，过拟合的风险很小，因为模型非常简单（或受限）。然而，对于更高维的数据集（即有大量特征的数据集），线性模型将变得更加强大，过拟合的可能性也会变大。我们来看一下 LinearRegression 在更复杂的数据集上的表现，比如波士顿房价数据集。记住，这个数据集有 506 个样本和 105个导出特征。首先，加载数据集并将其分为训练集和测试集。然后像前面一样构建线性回归模型："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "X, y= mglearn.datasets.load_extended_boston()\n",
    "\n",
    "X_train, X_test, y_train, y_test = train_test_split(X,y, random_state=0)\n",
    "lr=LinearRegression().fit(X_train, y_train)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "比较一下训练集和测试集的分数就可以发现，我们在训练集上的预测非常准确，但测试集上的 $R^2$ 要低很多："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "print(\"Training set score: {:.2f}\".format(lr.score(X_train, y_train)))\n",
    "print(\"Test set score: {:.2f}\".format(lr.score(X_test, y_test)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "训练集和测试集之间的性能差异是过拟合的明显标志，因此我们应该试图找到一个可以控制复杂度的模型。标准线性回归最常用的替代方法之一就是岭回归（ridge regression），下面来看一下。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 3. 岭回归\n",
    "岭回归也是一种用于回归的线性模型，因此它的预测公式与普通最小二乘法相同。但在岭回归中，对系数（w）的选择不仅要在训练数据上得到好的预测结果，而且还要拟合附加约束。我们还希望系数尽量小。换句话说，w 的所有元素都应接近于 0。直观上来看，这意味着每个特征对输出的影响应尽可能小（即斜率很小），同时仍给出很好的预测结果。这种约束是所谓正则化（regularization）的一个例子。正则化是指对模型做显式约束，以避免过拟合。岭回归用到的这种被称为 L2 正则化。\n",
    "\n",
    "岭回归在 linear_model.Ridge 中实现。来看一下它对扩展的波士顿房价数据集的效果如何"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from sklearn.linear_model import Ridge\n",
    "\n",
    "ridge = Ridge().fit(X_train, y_train)\n",
    "print(\"Training set score: {:.2f}\".format(ridge.score(X_train, y_train)))\n",
    "print(\"Test set score: {:.2f}\".format(ridge.score(X_test, y_test)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以看出， Ridge 在训练集上的分数要低于 LinearRegression ，但在测试集上的分数更高。这和我们的预期一致。线性回归对数据存在过拟合。 Ridge 是一种约束更强的模型，所以更不容易过拟合。复杂度更小的模型意味着在训练集上的性能更差，但泛化性能更好。由于我们只对泛化性能感兴趣，所以应该选择 Ridge 模型而不是 LinearRegression 模型。\n",
    "\n",
    "Ridge 模型在模型的简单性（系数都接近于 0）与训练集性能之间做出权衡。简单性和训练集性能二者对于模型的重要程度可以由用户通过设置 alpha 参数来指定。在前面的例子中，我们用的是默认参数 alpha=1.0 。但没有理由认为这会给出最佳权衡。 alpha 的最佳设定值取决于用到的具体数据集。增大 alpha 会使得系数更加趋向于 0，从而降低训练集性能，但可能会提高泛化性能。例如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "ridge10=Ridge(alpha=10).fit(X_train, y_train)\n",
    "print(\"Training set score: {:.2f}\".format(ridge10.score(X_train, y_train)))\n",
    "print(\"Test set score: {:.2f}\".format(ridge10.score(X_test, y_test)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "减小 alpha 可以让系数受到的限制更小。对于非常小的 alpha 值，系数几乎没有受到限制，我们得到一个与 LinearRegression 类似的模型："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "ridge01 = Ridge(alpha=0.1).fit(X_train, y_train)\n",
    "print(\"Training set score: {:.2f}\".format(ridge01.score(X_train, y_train)))\n",
    "print(\"Test set score: {:.2f}\".format(ridge01.score(X_test, y_test)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里 alpha=0.1 似乎效果不错。我们可以尝试进一步减小 alpha 以提高泛化性能。第 5 章将会讨论选择参数的正确方法。\n",
    "\n",
    "我们还可以查看 alpha 取不同值时模型的 coef\\_ 属性，从而更加定性地理解 alpha 参数是如何改变模型的。更大的 alpha 表示约束更强的模型，所以我们预计大 alpha 对应的 coef\\_ 元素比小 alpha 对应的 coef\\_ 元素要小。这一点可以在图 2-12 中得到证实："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "plt.plot(ridge.coef_, 's', label=\"Ridge alpha=1\")\n",
    "plt.plot(ridge10.coef_, '^', label=\"Ridge alpha=10\")\n",
    "plt.plot(ridge01.coef_, 'v', label=\"Ridge alpha=0.1\")\n",
    "plt.plot(lr.coef_, 'o', label=\"LinearRegression\")\n",
    "plt.xlabel(\"Coefficient index\")\n",
    "plt.ylabel(\"Coefficient magnitude\")\n",
    "plt.hlines(0, 0, len(lr.coef_))\n",
    "plt.ylim(-25, 25)\n",
    "plt.legend()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center><img src=\"imgs/2-12.jpg\" width=50%></center>\n",
    "<center><b>图 2-12：不同 alpha 值的岭回归与线性回归的系数比较</b></center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里 x 轴对应 coef_ 的元素： x=0 对应第一个特征的系数， x=1 对应第二个特征的系数，以此类推，一直到 x=100 。y 轴表示该系数的具体数值。这里需要记住的是，对于 alpha=10 ，系数大多在 -3 和 3 之间。对于 alpha=1 的 Ridge 模型，系数要稍大一点。对于 alpha=0.1 ，点的范围更大。对于没有做正则化的线性回归（即 alpha=0 ），点的范围很大，许多点都超出了图像的范围。  \n",
    "\n",
    "还有一种方法可以用来理解正则化的影响，就是固定 alpha 值，但改变训练数据量。对于图 2-13 来说，我们对波士顿房价数据集做二次抽样，并在数据量逐渐增加的子数据集上分别对 LinearRegression 和 Ridge(alpha=1) 两个模型进行评估（将模型性能作为数据集大小的函数进行绘图，这样的图像叫作学习曲线):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "mglearn.plots.plot_ridge_n_samples()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center><img src=\"imgs/2-13.jpg\" width=50%></center>\n",
    "<center><b>图 2-13：岭回归和线性回归在波士顿房价数据集上的学习曲线</b></center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "正如所预计的那样，无论是岭回归还是线性回归，所有数据集大小对应的训练分数都要高于测试分数。由于岭回归是正则化的，因此它的训练分数要整体低于线性回归的训练分数。但岭回归的测试分数要更高，特别是对较小的子数据集。如果少于 400 个数据点，线性回归学不到任何内容。随着模型可用的数据越来越多，两个模型的性能都在提升，最终线性回归的性能追上了岭回归。这里要记住的是，如果有足够多的训练数据，正则化变得不那么重要，并且岭回归和线性回归将具有相同的性能（在这个例子中，二者相同恰好发生在整个数据集的情况下，这只是一个巧合）。图 2-13 中还有一个有趣之处，就是线性回归的训练性能在下降。如果添加更多数据，模型将更加难以过拟合或记住所有的数据。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4. lasso\n",
    "除了 Ridge ，还有一种正则化的线性回归是 Lasso 。与岭回归相同，使用 lasso 也是约束系数使其接近于 0，但用到的方法不同，叫作 L1 正则化。L1 正则化的结果是，使用 lasso 时某些系数刚好为 0。这说明某些特征被模型完全忽略。这可以看作是一种自动化的特征选择。某些系数刚好为 0，这样模型更容易解释，也可以呈现模型最重要的特征。\n",
    "\n",
    "我们将 lasso 应用在扩展的波士顿房价数据集上："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from sklearn.linear_model import Lasso\n",
    "\n",
    "lasso = Lasso().fit(X_train, y_train)\n",
    "print(\"Training set score: {:.2f}\".format(lasso.score(X_train, y_train)))\n",
    "print(\"Test set score: {:.2f}\".format(lasso.score(X_test, y_test)))\n",
    "print(\"Number of features used: {}\".format(np.sum(lasso.coef_ != 0)))\n",
    "print(\"Number of all feature: {}\".format(lasso.coef_.shape[0]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如你所见， Lasso 在训练集与测试集上的表现都很差。这表示存在欠拟合，我们发现模型只用到了 105 个特征中的 4 个。与 Ridge 类似， Lasso 也有一个正则化参数 alpha ，可以控制系数趋向于 0 的强度。在上一个例子中，我们用的是默认值 alpha=1.0 。为了降低欠拟合，我们尝试减小 alpha 。这么做的同时，我们还需要增加 max_iter 的值（运行迭代的最大次数）："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 我们增大max_iter的值，否则模型会警告我们，说应该增大max_iter\n",
    "lasso001 = Lasso(alpha=0.01, max_iter=100000).fit(X_train, y_train)\n",
    "print(\"Training set score: {:.2f}\".format(lasso001.score(X_train, y_train)))\n",
    "print(\"Test set score: {:.2f}\".format(lasso001.score(X_test, y_test)))\n",
    "print(\"Number of features used: {}\".format(np.sum(lasso001.coef_ != 0)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "alpha 值变小，我们可以拟合一个更复杂的模型，在训练集和测试集上的表现也更好。模型性能比使用 Ridge 时略好一点，而且我们只用到了 105 个特征中的 33 个。这样模型可能更容易理解。\n",
    "\n",
    "但如果把 alpha 设得太小，那么就会消除正则化的效果，并出现过拟合，得到与LinearRegression 类似的结果："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "lasso00001 = Lasso(alpha=0.0001, max_iter=100000).fit(X_train, y_train)\n",
    "print(\"Training set score: {:.2f}\".format(lasso00001.score(X_train, y_train)))\n",
    "print(\"Test set score: {:.2f}\".format(lasso00001.score(X_test, y_test)))\n",
    "print(\"Number of features used: {}\".format(np.sum(lasso00001.coef_ != 0)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "再次像图 2-12 那样对不同模型的系数进行作图，见图 2-14："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "plt.plot(lasso.coef_, 's', label=\"Lasso alpha=1\")\n",
    "plt.plot(lasso001.coef_, '^', label=\"Lasso alpha=0.01\")\n",
    "plt.plot(lasso00001.coef_, 'v', label=\"Lasso alpha=0.0001\")\n",
    "plt.plot(ridge01.coef_, 'o', label=\"Ridge alpha=0.1\")\n",
    "plt.legend(ncol=2, loc=(0, 1.05))\n",
    "plt.ylim(-25, 25)\n",
    "plt.xlabel(\"Coefficient index\")\n",
    "plt.ylabel(\"Coefficient magnitude\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center><img src=\"imgs/2-14.jpg\" width=50%></center>\n",
    "<center><b>图 2-14：不同 alpha 值的 lasso 回归与岭回归的系数比较</b></center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在 alpha=1 时，我们发现不仅大部分系数都是 0（我们已经知道这一点），而且其他系数也都很小。将 alpha 减小至 0.01 ，我们得到图中向上的三角形，大部分特征等于 0。alpha=0.0001 时，我们得到正则化很弱的模型，大部分系数都不为 0，并且还很大。为了便于比较，图中用圆形表示 Ridge 的最佳结果。 alpha=0.1 的 Ridge 模型的预测性能与alpha=0.01 的 Lasso 模型类似，但 Ridge 模型的所有系数都不为 0。\n",
    "\n",
    "在实践中，在两个模型中一般首选岭回归。但如果特征很多，你认为只有其中几个是重要的，那么选择 Lasso 可能更好。同样，如果你想要一个容易解释的模型， Lasso 可以给出更容易理解的模型，因为它只选择了一部分输入特征。 scikit-learn 还提供了 ElasticNet类，结合了 Lasso 和 Ridge 的惩罚项。在实践中，这种结合的效果最好，不过代价是要调节两个参数：一个用于 L1 正则化，一个用于 L2 正则化。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 5. 用于分类的线性模型\n",
    "线性模型也广泛应用于分类问题。我们首先来看二分类。这时可以利用下面的公式进行\n",
    "预测：\n",
    "\n",
    "$$ŷ = w[0] * x[0] + w[1] * x[1] + …+ w[p] * x[p] + b > 0$$\n",
    "\n",
    "这个公式看起来与线性回归的公式非常相似，但我们没有返回特征的加权求和，而是为预测设置了阈值（0）。如果函数值小于 0，我们就预测类别 -1；如果函数值大于 0，我们就预测类别 +1。对于所有用于分类的线性模型，这个预测规则都是通用的。同样，有很多种不同的方法来找出系数（w）和截距（b）。\n",
    "\n",
    "对于用于回归的线性模型，输出 ŷ 是特征的线性函数，是直线、平面或超平面（对于更高维的数据集）。对于用于分类的线性模型，**决策边界**是输入的线性函数。换句话说，（二元）线性分类器是利用直线、平面或超平面来分开两个类别的分类器。本节我们将看到这方面的例子。\n",
    "\n",
    "学习线性模型有很多种算法。这些算法的区别在于以下两点：\n",
    "- 系数和截距的特定组合对训练数据拟合好坏的度量方法；\n",
    "- 是否使用正则化，以及使用哪种正则化方法。\n",
    "\n",
    "不同的算法使用不同的方法来度量“对训练集拟合好坏”。由于数学上的技术原因，不可能调节 w 和 b 使得算法产生的误分类数量最少。对于我们的目的，以及对于许多应用而言，上面第一点（称为损失函数）的选择并不重要。\n",
    "\n",
    "最常见的两种线性分类算法是 **Logistic 回归**（logistic regression）和**线性支持向量机**（linear support vector machine，线性 SVM），前者在 linear_model.LogisticRegression 中实现，后者在 svm.LinearSVC （SVC 代表支持向量分类器）中实现。虽然 LogisticRegression的名字中含有回归（regression），但它是一种分类算法，并不是回归算法，不应与LinearRegression 混淆。\n",
    "\n",
    "我们可以将 LogisticRegression 和 LinearSVC 模型应用到 forge 数据集上，并将线性模型找到的决策边界可视化（图 2-15）："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.svm import LinearSVC\n",
    "\n",
    "X, y = mglearn.datasets.make_forge()\n",
    "\n",
    "figs, axes = plt.subplots(1,2, figsize=(10,3))\n",
    "\n",
    "for model, ax in zip([LinearSVC(), LogisticRegression()], axes):\n",
    "    clf = model.fit(X,y)\n",
    "    mglearn.plots.plot_2d_separator(clf, X, fill=False, eps=0.5, ax=ax, alpha=.7)\n",
    "    mglearn.discrete_scatter(X[:,0], X[:,1],y, ax=ax)\n",
    "    ax.set_title(\"{}\".format(clf.__class__.__name__))\n",
    "    ax.set_xlabel(\"Feature 0\")\n",
    "    ax.set_ylabel(\"Feature 1\")\n",
    "axes[0].legend()    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center><img src=\"imgs/2-15.jpg\" width=80%></center>\n",
    "<center><b>图 2-15：线性 SVM 和 Logistic 回归在 forge 数据集上的决策边界（均为默认参数）</b></center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在这张图中， forge 数据集的第一个特征位于 x 轴，第二个特征位于 y 轴，与前面相同。图中分别展示了 LinearSVC 和 LogisticRegression 得到的决策边界，都是直线，将顶部归为类别 1 的区域和底部归为类别 0 的区域分开了。换句话说，对于每个分类器而言，位于黑线上方的新数据点都会被划为类别 1，而在黑线下方的点都会被划为类别 0。\n",
    "\n",
    "两个模型得到了相似的决策边界。注意，两个模型中都有两个点的分类是错误的。两个模型都默认使用 L2 正则化，就像 Ridge 对回归所做的那样。\n",
    "\n",
    "对于 LogisticRegression 和 LinearSVC ，决定正则化强度的权衡参数叫作 C 。 C 值越大，对应的正则化越弱。换句话说，如果参数 C 值较大，那么 LogisticRegression 和LinearSVC 将尽可能将训练集拟合到最好，而如果 C 值较小，那么模型更强调使系数向量（w）接近于 0。参数 C 的作用还有另一个有趣之处。较小的 C 值可以让算法尽量适应“大多数”数据点，而较大的 C 值更强调每个数据点都分类正确的重要性。下面是使用 LinearSVC 的图示（图 2-16）："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "mglearn.plots.plot_linear_svc_regularization()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center><img src=\"imgs/2-16.jpg\" width=80%></center>\n",
    "<center><b>图 2-16：不同 C 值的线性 SVM 在 forge 数据集上的决策边界</b></center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在左侧的图中， C 值很小，对应强正则化。大部分属于类别 0 的点都位于底部，大部分属于类别 1 的点都位于顶部。强正则化的模型会选择一条相对水平的线，有两个点分类错误。在中间的图中， C 值稍大，模型更关注两个分类错误的样本，使决策边界的斜率变大。最后，在右侧的图中，模型的 C 值非常大，使得决策边界的斜率也很大，现在模型对类别 0 中所有点的分类都是正确的。类别 1 中仍有一个点分类错误，这是因为对这个数据集来说，不可能用一条直线将所有点都分类正确。右侧图中的模型尽量使所有点的分类都正确，但可能无法掌握类别的整体分布。换句话说，这个模型很可能过拟合。\n",
    "\n",
    "与回归的情况类似，用于分类的线性模型在低维空间中看起来可能非常受限，决策边界只能是直线或平面。同样，在高维空间中，用于分类的线性模型变得非常强大，当考虑更多特征时，避免过拟合变得越来越重要。\n",
    "\n",
    "我们在乳腺癌数据集上详细分析 LogisticRegression ："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from sklearn.datasets import load_breast_cancer\n",
    "cancer = load_breast_cancer()\n",
    "X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, stratify=cancer.target, random_state=42)\n",
    "logreg = LogisticRegression().fit(X_train, y_train)\n",
    "print(\"Training set score: {:.3f}\".format(logreg.score(X_train, y_train)))\n",
    "print(\"Test set score: {:.3f}\".format(logreg.score(X_test, y_test)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "C=1 的默认值给出了相当好的性能，在训练集和测试集上都达到 95% 的精度。但由于训练集和测试集的性能非常接近，所以模型很可能是欠拟合的。我们尝试增大 C 来拟合一个更灵活的模型："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "logreg100 = LogisticRegression(C=100).fit(X_train, y_train)\n",
    "print(\"Training set score: {:.3f}\".format(logreg100.score(X_train, y_train)))\n",
    "print(\"Test set score: {:.3f}\".format(logreg100.score(X_test, y_test)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用 C=100 可以得到更高的训练集精度，也得到了稍高的测试集精度，这也证实了我们的直觉，即更复杂的模型应该性能更好。\n",
    "我们还可以研究使用正则化更强的模型时会发生什么。设置 C=0.01 ："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "logreg001 = LogisticRegression(C=0.01).fit(X_train, y_train)\n",
    "print(\"Training set score: {:.3f}\".format(logreg001.score(X_train, y_train)))\n",
    "print(\"Test set score: {:.3f}\".format(logreg001.score(X_test, y_test)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "正如我们所料，在图 2-1 中将已经欠拟合的模型继续向左移动，训练集和测试集的精度都比采用默认参数时更小。\n",
    "\n",
    "最后，来看一下正则化参数 C 取三个不同的值时模型学到的系数（图 2-17）："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "plt.plot(logreg.coef_.T, 'o', label=\"C=1\")\n",
    "plt.plot(logreg100.coef_.T, '^', label=\"C=100\")\n",
    "plt.plot(logreg001.coef_.T, 'v', label=\"C=0.001\")\n",
    "plt.xticks(range(cancer.data.shape[1]), cancer.feature_names, rotation=90)\n",
    "plt.hlines(0, 0, cancer.data.shape[1])\n",
    "plt.ylim(-5, 5)\n",
    "plt.xlabel(\"Coefficient index\")\n",
    "plt.ylabel(\"Coefficient magnitude\")\n",
    "plt.legend()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center><img src=\"imgs/2-17.jpg\" width=50%></center>\n",
    "<center><b>图 2-17：不同 C 值的 Logistic 回归在乳腺癌数据集上学到的系数</b></center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "由于 LogisticRegression 默认应用 L2 正则化，所以其结果与图 2-12 中 Ridge 的结果类似。更强的正则化使得系数更趋向于 0，但系数永远不会正好等于 0。进一步观察图像，还可以在第 3 个系数那里发现有趣之处，这个系数是“平均周长”（mean perimeter）。C=100 和 C=1 时，这个系数为负，而C=0.001 时这个系数为正，其绝对值比 C=1 时还要大。在解释这样的模型时，人们可能会认为，系数可以告诉我们某个特征与哪个类别有关。例如，人们可能会认为高“纹理错误”（texture error）特征与“恶性”样本有关。但“平均周长”系数的正负号发生变化，说明较大的“平均周长”可以被当作“良性”的指标或“恶性”的指标，具体取决于我们考虑的是哪个模型。这也说明，对线性模型系数的解释应该始终持保留态度。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果想要一个可解释性更强的模型，使用 L1 正则化可能更好，因为它约束模型只使用少\n",
    "数几个特征。下面是使用 L1 正则化的系数图像和分类精度（图 2-18）。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center><img src=\"imgs/2-18.jpg\" width=50%></center>\n",
    "<center><b>图 2-18：对于不同的 C 值，L1 惩罚的 Logistic 回归在乳腺癌数据集上学到的系数</b></center>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "for C, marker in zip([0.001, 1, 100], ['o', '^', 'v']):\n",
    "    lr_l1 = LogisticRegression(C=C, penalty=\"l1\").fit(X_train, y_train)\n",
    "    print(\"Training accuracy of l1 logreg with C={:.3f}: {:.2f}\".format(\n",
    "    C, lr_l1.score(X_train, y_train)))\n",
    "    print(\"Test accuracy of l1 logreg with C={:.3f}: {:.2f}\".format(\n",
    "    C, lr_l1.score(X_test, y_test)))\n",
    "    plt.plot(lr_l1.coef_.T, marker, label=\"C={:.3f}\".format(C))\n",
    "plt.xticks(range(cancer.data.shape[1]), cancer.feature_names, rotation=90)\n",
    "plt.hlines(0, 0, cancer.data.shape[1])\n",
    "plt.xlabel(\"Coefficient index\")\n",
    "plt.ylabel(\"Coefficient magnitude\")\n",
    "plt.ylim(-5, 5)\n",
    "plt.legend(loc=3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如你所见，用于二分类的线性模型与用于回归的线性模型有许多相似之处。与用于回归的线性模型一样，模型的主要差别在于 penalty 参数，这个参数会影响正则化，也会影响模型是使用所有可用特征还是只选择特征的一个子集。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 6. 用于多分类的线性模型\n",
    "许多线性分类模型只适用于二分类问题，不能轻易推广到多类别问题（除了 Logistic 回归）。将二分类算法推广到多分类算法的一种常见方法是“一对其余”（one-vs.-rest）方法。在“一对其余”方法中，对每个类别都学习一个二分类模型，将这个类别与所有其他类别尽量分开，这样就生成了与类别个数一样多的二分类模型。在测试点上运行所有二类分类器来进行预测。在对应类别上分数最高的分类器“胜出”，将这个类别标签返回作为预测结果。\n",
    "\n",
    "每个类别都对应一个二类分类器，这样每个类别也都有一个系数（w）向量和一个截距（b）。下面给出的是分类置信方程，其结果中最大值对应的类别即为预测的类别标签：\n",
    "\n",
    "$$ w[0] * x[0] + w[1] * x[1] + … + w[p] * x[p] + b$$\n",
    "\n",
    "多分类 Logistic 回归背后的数学与“一对其余”方法稍有不同，但它也是对每个类别都有一个系数向量和一个截距，也使用了相同的预测方法。\n",
    "\n",
    "我们将“一对其余”方法应用在一个简单的三分类数据集上。我们用到了一个二维数据集，每个类别的数据都是从一个高斯分布中采样得出的（见图 2-19）："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from sklearn.datasets import make_blobs\n",
    "\n",
    "X, y=make_blobs(random_state=42)\n",
    "mglearn.discrete_scatter(X[:,0],X[:,1],y)\n",
    "plt.xlabel(\"Feature 0\")\n",
    "plt.ylabel(\"Feature 1\")\n",
    "plt.legend([\"Class 0\", \"Class 1\", \"Class 2\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center><img src=\"imgs/2-19.jpg\" width=50%></center>\n",
    "<center><b>图 2-19：包含 3 个类别的二维玩具数据集</b></center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在，在这个数据集上训练一个 LinearSVC 分类器："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "linear_svm = LinearSVC().fit(X, y)\n",
    "print(\"Coefficient shape: \", linear_svm.coef_.shape)\n",
    "print(\"Intercept shape: \", linear_svm.intercept_.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们看到， coef\\_ 的形状是 (3, 2) ，说明 coef\\_ 每行包含三个类别之一的系数向量，每列包含某个特征（这个数据集有 2 个特征）对应的系数值。现在 intercept_ 是一维数组，保存每个类别的截距。\n",
    "\n",
    "我们将这 3 个二类分类器给出的直线可视化（图 2-20）："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "mglearn.discrete_scatter(X[:, 0], X[:, 1], y)\n",
    "line = np.linspace(-15, 15)\n",
    "for coef, intercept, color in zip(linear_svm.coef_, linear_svm.intercept_, ['b','r','g']):\n",
    "    plt.plot(line, -(line*coef[0]+intercept)/coef[1], c=color)\n",
    "plt.ylim(-10,15)\n",
    "plt.xlim(-10, 8)\n",
    "plt.xlabel(\"Feature 0\")\n",
    "plt.ylabel(\"Feature 1\")\n",
    "plt.legend(['Class 0', 'Class 1', 'Class 2', 'Line class 0', 'Line class 1',\n",
    "'Line class 2'], loc=(1.01, 0.3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center><img src=\"imgs/2-20.jpg\" width=50%></center>\n",
    "<center><b>图 2-20：三个“一对其余”分类器学到的决策边界</b></center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "你可以看到，训练集中所有属于类别 0 的点都在与类别 0 对应的直线上方，这说明它们位于这个二类分类器属于“类别 0”的那一侧。属于类别 0 的点位于与类别 2 对应的直线上方，这说明它们被类别 2 的二类分类器划为“其余”。属于类别 0 的点位于与类别 1 对应的直线左侧，这说明类别 1 的二元分类器将它们划为“其余”。因此，这一区域的所有点都会被最终分类器划为类别 0（类别 0 的分类器的分类置信方程的结果大于 0，其他两个类别对应的结果都小于 0）。\n",
    "\n",
    "但图像中间的三角形区域属于哪一个类别呢，3 个二类分类器都将这一区域内的点划为“其余”。这里的点应该划归到哪一个类别呢？答案是分类方程结果最大的那个类别，即最接近的那条线对应的类别。下面的例子（图 2-21）给出了二维空间中所有区域的预测结果："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "mglearn.plots.plot_2d_classification(linear_svm, X, fill=True, alpha=.7)\n",
    "mglearn.discrete_scatter(X[:, 0], X[:, 1], y)\n",
    "line = np.linspace(-15, 15)\n",
    "for coef, intercept, color in zip(linear_svm.coef_, linear_svm.intercept_,\n",
    "['b', 'r', 'g']):\n",
    "    plt.plot(line, -(line * coef[0] + intercept) / coef[1], c=color)\n",
    "plt.legend(['Class 0', 'Class 1', 'Class 2', 'Line class 0', 'Line class 1',\n",
    "'Line class 2'], loc=(1.01, 0.3))\n",
    "plt.xlabel(\"Feature 0\")\n",
    "plt.ylabel(\"Feature 1\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center><img src=\"imgs/2-21.jpg\" width=50%></center>\n",
    "<center><b>图 2-21：三个“一对其余”分类器得到的多分类决策边界</b></center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 7. 优点、缺点和参数\n",
    "线性模型的主要参数是正则化参数，在回归模型中叫作 alpha ，在 LinearSVC 和 Logistic-Regression 中叫作 C 。 alpha 值较大或 C 值较小，说明模型比较简单。特别是对于回归模型而言，调节这些参数非常重要。通常在对数尺度上对 C 和 alpha 进行搜索。你还需要确定的是用 L1 正则化还是 L2 正则化。如果你假定只有几个特征是真正重要的，那么你应该用L1 正则化，否则应默认使用 L2 正则化。如果模型的可解释性很重要的话，使用 L1 也会有帮助。由于 L1 只用到几个特征，所以更容易解释哪些特征对模型是重要的，以及这些特征的作用。\n",
    "\n",
    "**线性模型的训练速度非常快，预测速度也很快。这种模型可以推广到非常大的数据集，对稀疏数据也很有效**。如果你的数据包含数十万甚至上百万个样本，你可能需要研究如何使用 LogisticRegression 和 Ridge 模型的 solver='sag' 选项，在处理大型数据时，这一选项比默认值要更快。其他选项还有 SGDClassifier 类和 SGDRegressor 类，它们对本节介绍的线性模型实现了可扩展性更强的版本。\n",
    "\n",
    "线性模型的另一个优点在于，利用我们之间见过的用于回归和分类的公式，理解如何进行预测是相对比较容易的。不幸的是，往往并不完全清楚系数为什么是这样的。如果你的数据集中包含高度相关的特征，这一问题尤为突出。在这种情况下，可能很难对系数做出解释。\n",
    "\n",
    "**如果特征数量大于样本数量，线性模型的表现通常都很好**。它也常用于非常大的数据集，只是因为训练其他模型并不可行。但在更低维的空间中，其他模型的泛化性能可能更好。2.3.7 节会介绍几个线性模型不适用的例子。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python [conda env:Anaconda3]",
   "language": "python",
   "name": "conda-env-Anaconda3-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
